在這之前我們已經有了 TypeScript 型別系統的基礎,尤其在基本型別、物件、陣列、元組、 enum 和最重要的 TypeScript 註記與型別推論的概念。下一步我們就要正式步入學寫程式碼的境界了,畢竟函式型程式設計師是在學習大專案之前最常使用的。在學習函數或函式之前,如果還不到了解的話請不用緊張,我們會從最簡單的結構到實際應用上的解說。首先先來看之前偶而會見到的 function 吧~
function add(a: number, b: number){
return a + b;
}
應該有一點眼熟~在之前的章節我會偶而偷偷放出這樣的程式碼,而這也大部分程式語言都會有的結構,請看下圖。
會發現圖中的結構跟之前的沒什麼太大差異,只是這一次我們帶入了參數的概念和回傳的概念。
參數:函示運作的時候,有時會需要帶入數值才能計算;而這帶入的數值就是參數,當然他可以帶入數值也可以帶入物件。
回傳:函式運算過程結束後,回傳一個答案給呼叫函式的人。
稍微暸解後,我們要來理解函示在 TypeScript 的部分了。
順便一提回傳的型別當然也可以指定型別,大概會長這樣子。
function add(a: number, b: number): number{
return a + b;
}
在 JavaScript 中,函示屬於一級物件(first-class object);這表示使用它跟使用其他物件的時候完全相同,一樣可以指定給變數、傳入其他函式。而在 TypeScript 也運用強大的型別系統盡可能跟著做。接下來我們會介紹其中常見的五種宣告函式的方式。
function hi1(name: string): string {
return 'hello ' + name;
}
let hi2 = function(name: string): string {
return 'hello ' + name;
}
let hi3 = (name: string): string => {
return 'hello ' + name;
}
let hi4 = (name: string): string =>
'hello ' + name;
let hi5 = new Function('name', 'return "hello " + name');
不太安全,不建議使用。因為該種方法使得參數與環傳型別屬於不具型別(untyped),所以你應該能想到他會變成做任何事情都暢行無阻;而 TypeScript 只能眼睜睜的看著…
這五種宣告函式的方法,除了第5種完全不推薦之外,其他都可以使用,當然函示與箭頭函式有一個細微的差異我們後續會解釋。
接下來我們會先著重在參數的理解上,其中參數與引數很容易搞混,這邊用個例子快速回顧一下。
function hi1(name: string): string { // name 這邊的稱為參數。
return 'hello ' + name;
}
let myName = 'dingding'
console.log(hi1(myName)); // myName 這邊的稱為引數。
參數( parameter ):函式運作時可能需要的東西。
引數( argument):呼叫函示運作時,給它的東西。
之前提到 Object 內部的元素是可以介於有跟 undefined 的,所以當然也有預設參數和選擇性參數羅。
function hi(name: string, studentId?: string) {
return 'hi ' + name + studentId;
}
選擇性參數一定要放在參數列表的最尾端。畢竟不會有人希望使用的時候還不小心用到選擇性參數吧,但事實上我們會盡可能避免選擇性的參數應用。
type Student = {
name: string,
id: string,
}
function hi1(student: Student) {
console.log(student.name, student.id);
}
透過上面的範例,可以考慮盡可能的把參數的組成也盡量地完成。這會避免掉很多因為型態上的錯誤。
之前我們都是將參數的數量給固定住,接下來要分享的是如果是任意數量的參數該怎麼使用呢?
在分享之前,我們得先分享 JavaScript 神奇的 arguments 物件。之所以用神奇來稱呼 arguments 它,是因為他可以像魔術一樣,將要傳傳入的引數變成一個類似陣列( array-like ),不是實質意義上的陣列,還需要在內部呼叫 .reduce 才能轉換回陣列。
function test(): number{
return Array
.from(arguments)
.reduce((total, n) => total + n, 0);
}
test(1, 2, 3); // 6
先說結論,它並不安全,因為很容易在使用過程中,像是 n, total 之類的型別都會是 any ,這也就代表 TypeScript 又只能眼睜睜的看著它們暢行無阻了...
function test(...numbers: number[]): number {
return number.reduce((total, n) => total + n, 0);
}
test(1, 2, 3); // 6
是不是變得非常簡單,而且還很安全。
一個函式只能有一個其餘參數( Rest Parameters ),並且必須要放在最後面當參數。
畢竟不放最後面,怎麼能知道是不是輪到他上場了?
我們在呼叫函式來替我們工作的時候,通常會用 「()」來呼叫,除此之外還有幾個不同的用法
function add(a: number, b: number): number{
return a + b;
}
add.call(null, 10, 20); // 30
call:會把一個數值聯繫(bind)到函式中的 this ,而上述例子連結到了 null,並且依序套用引數。
function add(a: number, b: number): number{
return a + b;
}
add.appy(null, [10, 20]); // 30
apply:會把一個數值聯繫(bind)到函式中的 this ,而上述例子連結到了 null,第二個引數就直接傳給函式的參數了。
function add(a: number, b: number): number{
return a + b;
}
add.bind(null, 10, 20)(); // 30
bind:一樣會把數值聯繫(bind)到函式中的 this,但不會直接使用我們的函式,而是回傳一個新的函式,也就是後面會額外有()的由來。當然也可以在後面繼續用 .call 和 .apply 。
如果並沒有從 JavaScript 過來到 TypeScript 的話,可能會跟我當初一樣驚訝, JavaScript 會每個函式建立並定義 this 變數。那他背後的知識可能會過多,我們先了解一件事情就好了。(這也是為什麼 function 與箭頭函式會說有那麼點不一樣了)。
this 的值會隨著呼叫的函式方式不同而變。簡單來說就是什麼時候用 this 它就會變成什麼。危險就在於很容易不小心叫錯 this 了。
let a = {
x() {
return this;
}
}
console.log(a.x()); // this 還正在指出 a 的 x。 也就是說,主體在 a 。
let x = a.x;
console.log(x()); // this 主體已經不知道變成什麼了。
這個舉例應該會很模糊,也可能非常的複雜,不過我們只要記得「 this 的值取決於我呼叫函式的方式,而非我宣告它的方式」,否則上面例子的結果應該要一樣呢。
幸好
幸好 TypeScript 專門在預防這種事情, TypeScript 強制我們在使用 this 的時候必續替他宣告預期的型別,確保不會出錯。當然,它必須放在函式參數的第一個。
function isMyBirthday(this: Date, birthday: Date){
return this.getDate() === birthday.getDate(); // 這是一個示例,並不能成功運行唷~
}